﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTFF.WPF.Model.Lyrics;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Xml;
using System.Web;
using iTFF.WPF.Helpers;
using System.Resources;

namespace iTFF.WPF.CP
{

    class LyricsControlPanel
    {

        #region Fields

        ILyricsLocalProvider[] _localProviders;
        LyricsWebProvider[] _webProviders;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the list of available local providers
        /// </summary>
        public ILyricsLocalProvider[] LocalProviders
        {
            get
            {
                if (_localProviders == null)
                {
                    var ass = System.Reflection.Assembly.GetExecutingAssembly();
                    _localProviders = ass.GetTypes()
                        .Where(t => t.GetInterface(typeof(ILyricsLocalProvider).FullName) != null)
                        .Select(t => ass.CreateInstance(t.FullName) as ILyricsLocalProvider)
                        .ToArray();
                }

                return _localProviders;

            }
        }

        /// <summary>
        /// Gets the Web Providers
        /// </summary>
        public LyricsWebProvider[] WebProviders
        {
            get
            {
                if (_webProviders == null)
                    _webProviders = ParseWebProviders();

                return _webProviders;
            }
        }

        /// <summary>
        /// The activated local providers
        /// </summary>
        public IEnumerable<ILyricsLocalProvider> ActivatedAndValidLocalProviders
        {
            get { return LocalProviders.Where(lp => lp.IsActivated && lp.IsValid); }
        }

        #endregion

        #region Public helpers

        /// <summary>
        /// Gets the Web provider by its name
        /// </summary>
        /// <param name="name"></param>
        /// <returns>Returns the web provider or null if no provider is found</returns>
        public LyricsWebProvider GetWebProvider(string name)
        {
            return WebProviders.FirstOrDefault(wp => wp.Name == name);
        }

        /// <summary>
        /// Gets the web provider by its type
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public ILyricsLocalProvider GetLocalProvider(Type t)
        {
            return LocalProviders.FirstOrDefault(lp => lp.GetType() == t);
        }

        #endregion

        #region Web providers

        /// <summary>
        /// Opens the xml resource file and create the web providers
        /// </summary>
        /// <returns></returns>
        private LyricsWebProvider[] ParseWebProviders()
        {
            XDocument xdoc = XDocument.Parse(Resources.Resources.LyricsProviders);

            var providers =
                from p in xdoc.Element("providers").Elements("provider")
                select new LyricsWebProvider
                {
                    Name = p.Element("name") == null ? null : p.Element("name").Value,
                    Url = p.Element("url") == null ? null : p.Element("url").Value,
                    IsFeelingLucky = p.Element("googleIsFeelingLucky") == null ? false : bool.Parse(p.Element("googleIsFeelingLucky").Value),
                    EncodedUrl = p.Element("encodedUrl") == null || p.Element("encodedUrl").Element("url") == null ? null : p.Element("encodedUrl").Element("url").Value,

                    AppendSearchSuggestionUrl = p.Element("encodedUrl") == null || p.Element("encodedUrl").Element("appendSearchSuggestionUrl") == null ? false : bool.Parse(p.Element("encodedUrl").Element("appendSearchSuggestionUrl").Value),
                    ClassName = p.Element("className") == null ? null : p.Element("className").Value,
                    MethodName = p.Element("methodName") == null ? null : p.Element("methodName").Value,
                    ResultsOnLyricsPageRegex =
                        p.Element("regex") == null || p.Element("regex").Element("resultsOnLyricsPage") == null ? null : new Regex(p.Element("regex").Element("resultsOnLyricsPage").Value, RegexOptions.IgnoreCase | RegexOptions.Singleline),
                    FirstLinkOnSearchPageRegex =
                        p.Element("regex") == null || p.Element("regex").Element("firstLinkOnSearchPage") == null ? null : new Regex(p.Element("regex").Element("firstLinkOnSearchPage").Value, RegexOptions.IgnoreCase | RegexOptions.Singleline),
                    Replacements = p.Element("replacements") == null ? null : GetXmlReplacements(p.Element("replacements"))
                };

            return providers.ToArray();
        }

        /// <summary>
        /// Gets the xmlReplacements part from the xml as an array
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private string[][] GetXmlReplacements(XElement xElement)
        {
            List<string[]> replacements = new List<string[]>();

            var rep =
                from r in xElement.Elements("replacement")
                select new { Old = r.Attribute("oldStr").Value, New = r.Attribute("newStr").Value };

            foreach (var item in rep)
            {
                if (!string.IsNullOrEmpty(item.Old) && !string.IsNullOrEmpty(item.New))
                    replacements.Add(new string[] { item.Old, item.New });
            }

            if (replacements.Count == 0)
                return null;
            else
                return replacements.ToArray();
        }

        /// <summary>
        /// Downloads the lyrics. Tries all the providers until the lyrics are found
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public LyricsModel FetchLyrics(string artist, string name)
        {
            if (artist == null)
                throw new ArgumentNullException("artist");

            if (name == null)
                throw new ArgumentNullException("name");

            // search only if there is at least a valid artist and name
            if (string.IsNullOrEmpty(artist) || string.IsNullOrEmpty(name))
                return null;

            foreach (var pro in WebProviders)
            {

                var l = FetchLyrics(artist, name, pro);
                if (l != null)
                    return l;
            }

            return null;
        }

        /// <summary>
        /// Downloads the lyrics for the specified provider
        /// </summary>
        /// <param name="track"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public LyricsModel FetchLyrics(string artist, string name, LyricsWebProvider p)
        {
            if (artist == null)
                throw new ArgumentNullException("artist");

            if (name == null)
                throw new ArgumentNullException("name");

            if (p == null)
                throw new ArgumentNullException("p");

            // search only if there is at least a valid artist and name
            if (string.IsNullOrEmpty(artist) || string.IsNullOrEmpty(name))
                return null;


            if (!string.IsNullOrEmpty(p.EncodedUrl))
            {
                // means that we will use web page parsing
                string lyricsUrl = null;

                // means that we must first do a search and parse this page
                if (p.FirstLinkOnSearchPageRegex != null)
                {
                    string url = p.EncodedUrl
                        .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, artist)))
                        .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, name)));

                    string resp = WebHelper.SubmitRequestAndGetResponse(url, null, false);

                    if (resp == null)
                        return null;

                    // parse the result
                    Match m = p.FirstLinkOnSearchPageRegex.Match(resp);

                    if (m.Groups["url"].Success && !string.IsNullOrEmpty(m.Groups["url"].Value))
                    {
                        // get the final url
                        lyricsUrl = p.AppendSearchSuggestionUrl ? p.Url + m.Groups["url"].Value : m.Groups["url"].Value;

                    }
                    else
                        return null;
                }

                if (p.ResultsOnLyricsPageRegex != null)
                {
                    // if we already have the url from the result page, simply get it
                    string result;
                    if (lyricsUrl != null)
                        result = WebHelper.SubmitRequestAndGetResponse(lyricsUrl, null, false);
                    else
                    {
                        string url =
                            p.EncodedUrl
                            .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, artist)))
                            .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, name)));

                        // use google feeling luck if asked
                        if (p.IsFeelingLucky)
                        {
                            var flurl = p.EncodedUrl
                                .Replace(' ', '+')
                                .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, artist)))
                                .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, name)));

                            result = WebHelper.GoogleFeelingLuckyRequest(flurl);
                        }
                        else
                            result = WebHelper.SubmitRequestAndGetResponse(url, null, false);
                    }

                    if (result == null)
                        return null;

                    // parse the result
                    Match m = p.ResultsOnLyricsPageRegex.Match(result);

                    LyricsModel model = new LyricsModel();
                    if (m.Groups["artist"].Success)
                        model.Artist = HttpUtility.HtmlDecode(m.Groups["artist"].Value);
                    if (m.Groups["song"].Success)
                        model.Name = HttpUtility.HtmlDecode(m.Groups["song"].Value);
                    if (m.Groups["lyrics"].Success)
                    {
                        var lyrics = m.Groups["lyrics"].Value;

                        // trim the lines
                        model.Lyrics = ApplyFormatToHTMLOutput(lyrics);
                    }

                    if (string.IsNullOrEmpty(model.Lyrics))
                        return null;

                    return model;

                }
                else
                    return null;
            }
            else if (!string.IsNullOrEmpty(p.ClassName) && !string.IsNullOrEmpty(p.MethodName))
            {

                // here we need to instaciate a class and call the method
                try
                {
                    Type c = Type.GetType(p.ClassName);
                    object instance = Activator.CreateInstance(c);
                    LyricsModel d = (LyricsModel)c.GetMethod(p.MethodName).Invoke(instance, new object[] { artist, name });

                    return d;
                }
                catch (Exception)
                {
                    return null;
                }
            }
            else
                return null;
        }

        /// <summary>
        /// Applies the strings replacements
        /// </summary>
        /// <param name="p"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private string ApplyReplacements(LyricsWebProvider p, string value)
        {
            if (p.Replacements == null)
                return value;

            foreach (var r in p.Replacements)
                value = value.Replace(r[0], r[1]);
            return value;
        }

        private string ApplyFormatToHTMLOutput(string lyrics)
        {
            lyrics = HttpUtility.HtmlDecode(lyrics);

            // replace <br />
            lyrics = lyrics.Replace("\r\n", "");
            lyrics = lyrics.Replace("\n", "");
            lyrics = lyrics.Replace("\t", "");
            lyrics = Regex.Replace(lyrics, "<br />[(\r\n)(\n)]", "\r\n");
            lyrics = Regex.Replace(lyrics, "<br />", "\r\n");

            // remove beginning and ending newlines
            var regBeg = new Regex(@"^\s*", RegexOptions.Singleline);
            var regEnd = new Regex(@"\s*$", RegexOptions.Singleline);
            lyrics = regBeg.Replace(lyrics, "");
            lyrics = regEnd.Replace(lyrics, "");

            // now split by \r\n
            var lines = lyrics.Split(new string[] {"\r\n"}, StringSplitOptions.None);

            var l2 = lines.Select(l => l.Trim());

            return string.Join("\r\n", l2.ToArray());
        }

        #endregion

        #region Local providers

        /// <summary>
        /// Gets the lyrics from the local store
        /// </summary>
        /// <param name="persistentID"></param>
        /// <returns></returns>
        public LyricsModel GetLyrics(ulong persistentID)
        {
            foreach (var pro in ActivatedAndValidLocalProviders.OrderBy(a => a.Metric))
            {
                var l = pro.GetLyrics(persistentID);
                if (l != null)
                    return l;
            }

            return null;
        }

        /// <summary>
        /// Checks if the lyrics are in any of the local stores
        /// </summary>
        /// <param name="persistentID"></param>
        /// <returns></returns>
        public bool LyricsExists(ulong persistentID)
        {
            foreach (var pro in ActivatedAndValidLocalProviders.OrderBy(a => a.Metric))
            {
                var l = pro.GetLyrics(persistentID);
                if (l != null)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Saves the lyrics into all the activated local providers
        /// </summary>
        /// <param name="model"></param>
        /// <param name="force"></param>
        /// <returns></returns>
        public bool SaveLyrics(LyricsModel model, bool force)
        {
            if (string.IsNullOrEmpty(model.Lyrics) || ActivatedAndValidLocalProviders.Count() == 0)
                return false;

            bool ok = true;

            foreach (var pro in ActivatedAndValidLocalProviders)
            {
                if (!pro.SaveLyrics(model, force))
                    ok = false;
            }

            return ok;
        }

        /// <summary>
        /// Removes the lyrics from all the activated local providers
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public bool RemoveLyrics(ulong iTunesPersistentID)
        {
            if (ActivatedAndValidLocalProviders.Count() == 0)
                return false;

            bool ok = true;

            foreach (var pro in ActivatedAndValidLocalProviders)
            {
                if (!pro.RemoveLyrics(iTunesPersistentID))
                    ok = false;
            }

            return ok;
        }


        #endregion
    }
}
